package com.nvwa.boot.stater.web.aop;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.nvwa.coding.util.json.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

/**
 * Controller日志切面
 * 通知执行顺序:环绕通知==》前置通知==》环绕通知==》后置通知==》返回通知/异常通知
 */
@Slf4j
@Aspect
public class LogControllerAspect {

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void logPointcut() {}

    /**
     * 前置通知，打印方法入参
     */
    @Before("logPointcut()")
    public void logBefore(JoinPoint joinPoint) {
    }

    /**
     * 环绕通知
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        LocalDateTime start = LocalDateTime.now();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getRemoteAddr();
        String url = request.getRequestURL().toString();
        String methodType = request.getMethod();
        String methodName = String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
        Map<String, Object> requestParamsByProceedingJoinPoint = getRequestParamsByProceedingJoinPoint(proceedingJoinPoint);
        log.info("HTTP REQ---IP:{}; URL:{}; MethodType:{}; Method:{}; Args:{}", ip, url, methodType, methodName,
                JsonUtil.toString(requestParamsByProceedingJoinPoint));
        Object result = proceedingJoinPoint.proceed();
        Long cost = Duration.between(start, LocalDateTime.now()).toMillis();
        if (result instanceof ResponseEntity) {
            log.info("HTTP RESP---IP:{}; URL:{}; cost:{}ms;", ip, url, cost);
        } else {
            log.info("HTTP RESP---IP:{}; URL:{}; cost:{}ms; result:{}", ip, url, cost, JsonUtil.toString((result)));
        }
        return result;
    }


    /**
     * 后置通知
     */
    @After("logPointcut()")
    public void doAfter(JoinPoint joinPoint) {
    }

    /**
     * 返回通知
     */
    @AfterReturning(returning = "result", pointcut = "logPointcut()")
    public void logReturn(JoinPoint joinPoint, Object result) throws Exception {
    }

    /**
     * 异常通知
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void logAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getRemoteAddr();
        String url = request.getRequestURL().toString();
        String methodType = request.getMethod();
        String methodName = String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        Map<String, Object> requestParamsByProceedingJoinPoint = getRequestParamsByJoinPoint(joinPoint);
        log.error("HTTP Error e.msg:{}; IP:{}; URL:{}; MethodType:{}; Method:{}; Args:{}",
                e.getMessage(), ip, url, methodType, methodName, JsonUtil.toString(requestParamsByProceedingJoinPoint));
    }

    /**
     * 获取入参
     */
    private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        Object[] paramValues = proceedingJoinPoint.getArgs();
        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            if (value instanceof HttpServletRequest) {
                continue;
            }
            if (value instanceof HttpServletResponse) {
                continue;
            }
            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], value);
        }
        return requestParams;
    }

}